/*
 * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2012 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote sproducts derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "DumpRenderTree.h"

// DRT Local headers
#include "APICast.h"
#include "AccessibilityController.h"
#include "GCController.h"
#include "LayoutTestController.h"
#include "WebCore/testing/js/WebCoreTestSupport.h"
#include "WebCoreSupport/DumpRenderTreeSupportManx.h"
#include "WebViewPrivate.h"
#include "webkit/WebView.h"

// psp2 only
#include <ctrl.h>
#include <kernel.h>
#include <libsysmodule.h>
#include <net.h>
#include <string>

// Resource path
#define URL_LIST_PATH "app0:/filelist.txt"
#define TEST_RESULT_FILE_EXTENSION "-actual.txt"
#define URL_PREFIX "http://dl.dropbox.com/u/71554578/"
#define TEST_PREFIX "LayoutTests/"
#define FSROOT_PREFIX "host0:"
#define ICU_PATH "app0:vs0/data/external/webcore/data/icu"

#define WEBVIEW_WIDTH 960
#define WEBVIEW_HEIGHT 544

#define RUN_LIMIT 1
#define START_LINE_PATH "host0:/log/startline.txt"
#define END_LINE_PATH "host0:/log/endline.txt"

// User main thread parameters
extern const char            sceUserMainThreadName[]        = "SceDumpRenderTree";
extern const int            sceUserMainThreadPriority    = SCE_KERNEL_DEFAULT_PRIORITY_USER;
extern const unsigned int    sceUserMainThreadStackSize    = SCE_KERNEL_STACK_SIZE_DEFAULT_USER_MAIN;

// Libc parameters
unsigned int sceLibcHeapExtendedAlloc = 1;
unsigned int sceLibcHeapSize = SCE_LIBC_HEAP_SIZE_EXTENDED_ALLOC_NO_LIMIT;

// Net parameters
static char sNetMemory[16 * 1024];

// Global variables
volatile bool done;
static bool printSeparators;
static int dumpPixels;
static int dumpTree = 1;
static int useTimeoutWatchdog = 1;
FILE* fp;
char filenameBuffer[2048];
RefPtr<LayoutTestController> gLayoutTestController;
WebView* webView;
AccessibilityController* axController = 0;
static GCController* gcController = 0;
Frame* gmainFrame;
bool waitForPolicy =false;
int waitToDumpWatchdog = 0;
JSGlobalContextRef gContext =0;
list<WebView*> webViewList;
string testURL;

static bool shouldLogFrameLoadDelegates(const string& pathOrURL)
{
    return pathOrURL.find("loading/") != string::npos;
}

static bool shouldOpenWebInspector(const string& pathOrURL)
{
    return pathOrURL.find("inspector/") != string::npos;
}

static bool shouldDumpAsText(const string& pathOrURL)
{
    return pathOrURL.find("dumpAsText/") != string::npos;
}

static bool shouldEnableDeveloperExtras(const string& pathOrURL)
{
    return true;
}

void dumpFrameScrollPosition(Frame* frame)
{
}

void displayWebView()
{
}

static void appendString(char*& target, char* string)
{
    target = strcat(target, string);
}

static string dumpFramesAsText(Frame* frame)
{
    CString innerText = DumpRenderTreeSupportManx::getInnerText(frame);
    string result = "";
    char str[innerText.length() + 1024];

    if (static_cast<Frame*>(webView->frame()) == frame) {
        sprintf(str, "%s\n", innerText.data());
        result += str;
    } else {
        CString frameName = DumpRenderTreeSupportManx::getFrameName(frame);
        sprintf(str, "\n--------\nFrame: '%s'\n--------\n%s\n", frameName.data(), innerText.data());
        result += str;
    }
    if (gLayoutTestController->dumpChildFramesAsText()) {
        list<Frame*> children = DumpRenderTreeSupportManx::getFrameChildren(frame);
        list<Frame*>::iterator it = children.begin();
        while (it != children.end()) {
            result += dumpFramesAsText(*it);
            it++;
        }
    }
    return result;
}

#if 0
static void dumpBackForwardListForWebView(WebKitWebView* view)
{
    printf("\n============== Back Forward List ==============\n");
    WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(view);

    // Print out all items in the list after prevTestBFItem, which was from the previous test
    // Gather items from the end of the list, the print them out from oldest to newest
    GList* itemsToPrint = 0;
    gint forwardListCount = webkit_web_back_forward_list_get_forward_length(bfList);
    for (int i = forwardListCount; i > 0; i--) {
        WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
        // something is wrong if the item from the last test is in the forward part of the b/f list
        ASSERT(item != prevTestBFItem);
        g_object_ref(item);
        itemsToPrint = g_list_prepend(itemsToPrint, item);
    }

    WebKitWebHistoryItem* currentItem = webkit_web_back_forward_list_get_current_item(bfList);
    g_object_ref(currentItem);
    itemsToPrint = g_list_prepend(itemsToPrint, currentItem);

    gint backListCount = webkit_web_back_forward_list_get_back_length(bfList);
    for (int i = -1; i >= -(backListCount); i--) {
        WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
        if (item == prevTestBFItem)
            break;
        g_object_ref(item);
        itemsToPrint = g_list_prepend(itemsToPrint, item);
    }

    for (GList* itemToPrint = itemsToPrint; itemToPrint; itemToPrint = g_list_next(itemToPrint)) {
        WebKitWebHistoryItem* item = WEBKIT_WEB_HISTORY_ITEM(itemToPrint->data);
        dumpHistoryItem(item, historyItemIndent, item == currentItem);
        g_object_unref(item);
    }

    g_list_free(itemsToPrint);
    printf("===============================================\n");
}
#endif


static void dumpBackForwardListForAllWebViews()
{
#if 0
    // Dump the back forward list of the main WebView first
    dumpBackForwardListForWebView(webView);

    // The view list is prepended. Reverse the list so we get the order right.
    for (GSList* currentView = g_slist_reverse(webViewList); currentView; currentView = g_slist_next(currentView))
        dumpBackForwardListForWebView(WEBKIT_WEB_VIEW(currentView->data));
#endif
}

void setWaitToDumpWatchdog(int timer)
{
    waitToDumpWatchdog = timer;
}

bool shouldSetWaitToDumpWatchdog()
{
    return !waitToDumpWatchdog && useTimeoutWatchdog;
}

static void invalidateAnyPreviousWaitToDumpWatchdog()
{
    if (waitToDumpWatchdog)
        waitToDumpWatchdog = 0;

    waitForPolicy = false;
}

bool fileOpen()
{
    fp = fopen(URL_LIST_PATH, "r");
    if (!fp) {
        printf("UrlListFile Open Failed\n");
        fclose(fp);
        return false;
    }

    return true;
}

void fileWriter(string* result)
{
#if 0
    string::size_type index = result->find_first_of('\n', 0);
    if (index == 0)
        return;
#endif

    // Look for "." //as a separator between the path or URL, and the pixel dump hash that follows.
    string outputTextFileName = string(URL_PREFIX) + TEST_PREFIX;
    outputTextFileName = testURL.substr(strlen(outputTextFileName.c_str()));
    size_t separatorPos = outputTextFileName.find_last_of(".");
#if 1
    if (separatorPos != string::npos) {
//        testURL = string(testURL, 0, separatorPos);
//        outputTextFileName = testURL.substr(strlen("file:///")) + TEST_RESULT_FILE_EXTENSION;
        outputTextFileName =  string(FSROOT_PREFIX) + string(outputTextFileName, 0, separatorPos)+ TEST_RESULT_FILE_EXTENSION;

        FILE* wfp;
        wfp = fopen(outputTextFileName.c_str(), "wb");
        if (!wfp) {
            printf("ERROR:ResultsFileOpenFailed\n");
            fclose(wfp);
        } else {
            fwrite(result->c_str(), 1, result->length(), wfp);
            fclose(wfp);
        }
        printf("--------------------------\n%s\n--------------------------\n", result->c_str());
    }
#else
    printf("==========================\n%s\n==========================\n", result->c_str());
#endif
}

void dump()
{
    invalidateAnyPreviousWaitToDumpWatchdog();
    if (dumpTree) {
        string result;
        CString responseMimeType;
        responseMimeType = DumpRenderTreeSupportManx::getResponseMimeType(gmainFrame);
        if (!strcmp(responseMimeType.data(), "text/plain")) {
            gLayoutTestController->setDumpAsText(true);
            gLayoutTestController->setGeneratePixelResults(false);
        }
        if (gLayoutTestController->dumpAsText())
            result = dumpFramesAsText(gmainFrame);
        else {
            CString dumpRenderTree = DumpRenderTreeSupportManx::dumpRenderTree(gmainFrame).data();
            result += dumpRenderTree.data();
        }
        if (result.empty()) {
            const char* errorMessage;
            if (gLayoutTestController->dumpAsText())
                errorMessage = "[documentElement innerText]";
            else if (gLayoutTestController->dumpDOMAsWebArchive())
                errorMessage = "[[mainFrame DOMDocument] webArchive]";
            else if (gLayoutTestController->dumpSourceAsWebArchive())
                errorMessage = "[[mainFrame dataSource] webArchive]";
            else
                errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
            printf("ERROR: nil result from %s\n", errorMessage);
        } else {
            // Dump Tests Reslut
            fileWriter(&result);
            if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive())
                dumpFrameScrollPosition(gmainFrame);

            // if (gLayoutTestController->dumpBackForwardList())
                // dumpBackForwardListForAllWebViews();
        }
    }
    if (dumpPixels
     && gLayoutTestController->generatePixelResults()
     && !gLayoutTestController->dumpDOMAsWebArchive()
     && !gLayoutTestController->dumpSourceAsWebArchive())
        printf("PixelresultsDump\n");
//        dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash());

    if (gLayoutTestController->dumpProgressFinishedCallback()) {
        // Destroy Layout Test Controller
        gLayoutTestController.clear();
        done = true;
    }
}

void webViewLoadStarted(Frame* frame)
{
    // Create Layout Test Controller
    string expectedPixelHash;
    gLayoutTestController = LayoutTestController::create(testURL, expectedPixelHash);

    gLayoutTestController->setIconDatabaseEnabled(false);

    if (shouldLogFrameLoadDelegates(testURL))
        gLayoutTestController->setDumpFrameLoadCallbacks(true);

    if (shouldEnableDeveloperExtras(testURL)) {
        gLayoutTestController->setDeveloperExtrasEnabled(true);
        if (shouldOpenWebInspector(testURL))
            gLayoutTestController->showWebInspector();
        if (shouldDumpAsText(testURL)) {
            gLayoutTestController->setDumpAsText(true);
            gLayoutTestController->setGeneratePixelResults(false);
        }
    }
}

static void webViewLoadFinished(Frame *frame)
{
    if (gLayoutTestController == NULL) return;
    gLayoutTestController->setDumpProgressFinishedCallback(true);
    if (gLayoutTestController->waitToDump()) {
        done = true;
        return;
    }

    gLayoutTestController->setDumpProgressFinishedCallback(false);
    dump();

    // The deprecated "load-finished" signal is triggered by postProgressFinishedNotification(),
    // so we can use it here in the DRT to provide the correct dump.
    if (gLayoutTestController->dumpProgressFinishedCallback())
        printf("postProgressFinishedNotification\n");

    // If developer extras enabled Web Inspector may have been open by the test.
    if (shouldEnableDeveloperExtras(testURL)) {
        gLayoutTestController->closeWebInspector();
        gLayoutTestController->setDeveloperExtrasEnabled(false);
    }

    if (gLayoutTestController->closeRemainingWindowsWhenComplete()) { }
    if (gLayoutTestController && gContext)
        WebCoreTestSupport::resetInternalsObject(gContext);

    // Destroy Layout Test Controller
    gLayoutTestController.clear();

    done = true;
}

static void addControllerToWindow(JSContextRef context, JSObjectRef windowObject, const char* controllerName, JSValueRef controller)
{
    JSStringRef controllerNameStr = JSStringCreateWithUTF8CString(controllerName);
    JSObjectSetProperty(context, windowObject, controllerNameStr, controller, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0);
    JSStringRelease(controllerNameStr); 
}

static void webViewWindowObjectCleared(WebView* view, Frame* frame, JSGlobalContextRef context, JSObjectRef windowObject)
{
    JSValueRef exception = 0;
    gContext =0;
    ASSERT(gLayoutTestController);

    gLayoutTestController->makeWindowObject(context, windowObject, &exception);
    ASSERT(!exception);

    gcController->makeWindowObject(context, windowObject, &exception);
    ASSERT(!exception);

//    axController->makeWindowObject(context, windowObject, &exception);
//    ASSERT(!exception);

    // addControllerToWindow(context, windowObject, "eventSender", makeEventSender(context, !frame));
    // addControllerToWindow(context, windowObject, "textInputController", makeTextInputController(context));
    gContext = context;
    WebCoreTestSupport::injectInternalsObject(gContext);
}

static bool webViewCallback(WebView *webView, void *userData, CallbackType type, const CallbackData *in, CallbackData *out)
{
    switch (type) {
    case CALLBACK_LOAD_STARTED:
        webViewLoadStarted((Frame*)in[0].type_void_ptr);
        return true;
    case CALLBACK_LOAD_FINISHED:
        webViewLoadFinished(0);
        return true;
    case CALLBACK_LOAD_FAILED:
        webViewLoadFinished(0);
        return true;
    case CALLBACK_WINDOW_OBJECT_CLEARED:
        if (!gLayoutTestController)
            return true;
        webViewWindowObjectCleared(webView, (Frame*)in[1].type_void_ptr, (JSGlobalContextRef)in[2].type_void_ptr, (JSObjectRef)in[3].type_void_ptr);
        return true;
    default:
        break;
    }
    return true;
}

static void runTest(const string& testPathOrURL)
{
    ASSERT(!testPathOrURL.empty());
    clock_t runStart = clock();
    testURL = string(testPathOrURL);

    done = false;
    webView->loadUrl(testURL.c_str());
    while (!done) { } // FIXME: should wait with kernel event

    clock_t runEnd = clock();
    float result = runEnd - runStart;
    printf("runt time : %f[s]\n", result/(1000 * 1000));

    // sleep 1 seconds to next test
    printf("Waiting for next test...\n");
    sceKernelDelayThread(1 * 1000 * 1000);

}

static void initilizeTest()
{
    webView = WebViewManager::create(WEBVIEW_WIDTH, WEBVIEW_HEIGHT);

    gmainFrame = static_cast<Frame*>(webView->frame());

    gcController = new GCController();
}

static void finalizeTest()
{
    if (gcController) {
        delete gcController;
        gcController = 0;
    }
    WebViewManager::destroy(webView);
//    WebViewManager::exit();
}

static bool checkRunLimit(int cnt)
{
    bool resetFlg = false;
    
    if (cnt < RUN_LIMIT)
        return resetFlg;

    // ���\�[�X���
    finalizeTest();

    // ������
    initilizeTest();

    resetFlg = true;
    return resetFlg;
}

static int checkStartLine()
{
    int startLine = 0;
    FILE* fpStart;
    char buffLine[128];
    
    // file open
    fpStart = fopen(START_LINE_PATH, "r");
    if (!fpStart) {
        printf("StartLineFile Open Failed\n");
        fclose(fpStart);
        return startLine;
    }
    // read
    fgets(buffLine, sizeof(buffLine), fpStart);
    startLine = atoi(buffLine);
    // file close
    fclose(fpStart);
    return startLine;
}

static void writeEndLine(int line)
{
    FILE* fpEnd;
    char buffLine[128];
    // file open
    fpEnd = fopen(END_LINE_PATH, "w");
    if (!fpEnd) {
        printf("EndLineFile Open Failed\n");
        fclose(fpEnd);
        return;
    }
    // write
    if (line >= 0)
        fprintf(fpEnd, "%d", line);
    else
        fprintf(fpEnd, "end", line);
    // file close
    fclose(fpEnd);
}

static void runTestingServerLoop()
{
    int cntLine = 0;
    int cntRun = 0;
    int cntTotalRun = 0;
    int startLine = checkStartLine();
    
    while (fgets(filenameBuffer, sizeof(filenameBuffer), fp)) {
        cntLine++;
        char* newLineCharacter = strchr(filenameBuffer, '\n');
        if (newLineCharacter)
            *newLineCharacter = '\0';

        // �I���e�X�g�m�F
        if (cntLine < startLine)
            continue;

        // ��s
        if (!strlen(filenameBuffer))
            continue;

        // # �R�����g�s�Ή�
        newLineCharacter = strchr(filenameBuffer, '#');
        if (newLineCharacter == filenameBuffer)
            continue;

        printf("==========================\nLine:%d\n%s\n==========================\n", cntLine, filenameBuffer);
        if (checkRunLimit(cntRun))
            cntRun = 0;
        runTest(string(URL_PREFIX) + filenameBuffer);
        cntRun++;
        cntTotalRun++;
        printf("runTest End Test run Total cnt : %d Period cnt : %d\n", cntTotalRun, cntRun);
        writeEndLine(cntLine);
        sceKernelPowerTick(SCE_KERNEL_POWER_TICK_DISABLE_AUTO_SUSPEND);
#if 0
        if (cntLine == 355 && cntLine != startLine) break;
        if (cntLine == 777 && cntLine != startLine) break;
        if (cntLine == 1000 && cntLine != startLine) break;
        if (cntLine == 1000) break;
#endif
    }
}

int main(int argc, char* argv[])
{
    clock_t testStart = clock();
    int err = SCE_OK;
    int pRes = 0;
#if 0
    // load ScePsp2Compat
    err = sceKernelLoadStartModule("app0:ScePsp2Compat.suprx", 0, 0, 0, 0, &pRes);
    ASSERT(err > SCE_OK);
#endif

    // initialize network library
    err = sceSysmoduleLoadModule(SCE_SYSMODULE_NET);
    ASSERT(err == SCE_OK);

    err = sceKernelLoadStartModule("app0:vita_jsextobj.suprx", 0, 0, 0, SCE_NULL, NULL);
    if( err < SCE_OK ) printf("error for loading vita_jsextobj module\n");

    err = setenv("ICU_DATA", ICU_PATH, 0);
    ASSERT(err == SCE_OK);

    SceNetInitParam param;
    param.memory = sNetMemory;
    param.size = sizeof(sNetMemory);
    param.flags = 0;
    err = sceNetInit(&param);
    ASSERT(err == SCE_OK);

    // sceKernelChangeThreadPriority(SCE_KERNEL_THREAD_ID_SELF, SCE_KERNEL_DEFAULT_PRIORITY_USER-10);

    // create webview instance
    WebViewManager::init(true, webViewCallback, 0);
    webView = WebViewManager::create(WEBVIEW_WIDTH, WEBVIEW_HEIGHT);
    // It is important to declare DRT is running early so when creating
    // web view mock clients are used instead of proper ones.
    DumpRenderTreeSupportManx::setDumpRenderTreeModeEnabled(true);

    gmainFrame = static_cast<Frame*>(webView->frame());

    if (!fileOpen())
        return 0;

    gcController = new GCController();
    axController = new AccessibilityController();

    runTestingServerLoop();

    delete gcController;
    gcController = 0;
    delete axController;
    axController = 0;
    fclose(fp);
    WebViewManager::destroy(webView);
//    WebViewManager::exit();

//    err = sceKernelStopUnloadModule(err, 0, 0, 0, SCE_NULL, &pRes);
    err = sceSysmoduleUnloadModule(SCE_SYSMODULE_NET);
    ASSERT(err == SCE_OK);

    clock_t testEnd = clock();
    float result = testEnd - testStart;
    printf("test total time : %f[s]\n", result/(1000 * 1000));

    writeEndLine(-1);

    return 0;
}
